//*****************************************************************************
//
// File Name	: 'ds1302.c'
// Title		: Bitbang driver for Dallas 1302 Real Time Clock chip
// Author		: Sami Kukkonen - Copyright (C) 2011
// Created		: 4.7.2011
// Version		: 0.6
// Target MCU	: Atmel AVR series
// Editor Tabs	: 4
//
// This code is distributed under the GNU Public License
//		which can be found at http://www.gnu.org/licenses/gpl.txt
//
//*****************************************************************************

#include <avr/io.h>
#include <stdlib.h> 

#include "global.h"

#include "ds1302light.h"

#include <util/delay.h>

/* TODO:
 * - Comments
 * - Small public functions to setting and disabling flags and TC data
 * - Hour mode set and auto-convert function
 * - Time and date correctness checking for SetTime function
 * - Datatypes for rtctable, months and days
 * - Global data buffer to store all RTC register data and auto update in GetTime function
*/

// Global variables

// Time conversion bitmasks
const uint8_t DS1302_TC_BITMASKS[] = {
	0x7F, // 7 bits | Seconds
	0x7F, // 7 bits | Minutes
	0xFF, // 8 bits | Hours (auto check for 24h mode)
	0x3F, // 6 bits | Day
	0x1F, // 5 bits | Month
	0x07, // 3 bits | Weekday
	0xFF, // 8 bits | Year
	0x7F, // 7 bits | Write Protect Bit
};
/*
// Time registers
RtcTimeType RtcTime;
// Status registers
RtcStatusType RtcStatus;
*/
/*************************************************************/
/********************** LOCAL FUNCTIONS **********************/
/*************************************************************/

void ds1302InitHW(void) {
	// Initialize control lines for output
	sbi(DS1302_DDR, DS1302_RST);
	sbi(DS1302_DDR, DS1302_SCLK);
	sbi(DS1302_DDR, DS1302_IO);
	// Set outputs to low state
	cbi(DS1302_PORT, DS1302_RST);
	cbi(DS1302_PORT, DS1302_SCLK);
	cbi(DS1302_PORT, DS1302_IO);
}

void ds1302SetIOInput(void) {
	cbi(DS1302_DDR, DS1302_IO);
	cbi(DS1302_PORT, DS1302_IO);
}

void ds1302SetIOOutput(void) {
	sbi(DS1302_DDR, DS1302_IO);
	cbi(DS1302_PORT, DS1302_IO);
}

void ds1302SendCommand(uint8_t command) {
	uint8_t txbit;
	int b;
	for (b = 0; b < 8; b++) {
		cbi(DS1302_PORT, DS1302_SCLK);		// Set clock to low
		txbit = ((command >> b) & 0x01);	// Shift bit from byte
		if (txbit == 1) {					// Set bit to output
			sbi(DS1302_PORT, DS1302_IO);
		} else {
			cbi(DS1302_PORT, DS1302_IO);
		}
		_delay_us(1);
		sbi(DS1302_PORT, DS1302_SCLK);		// Set clock to high (transfer)
		_delay_us(1);						// Leave clock high for Get
	}
}

void ds1302SendByte(uint8_t txbyte) {
	uint8_t txbit;
	int b;
	for (b = 0; b < 8; b++) {
		txbit = ((txbyte >> b) & 0x01);		// Shift bit from byte
		if (txbit == 1) {					// Set bit to output
			sbi(DS1302_PORT, DS1302_IO);
		} else {
			cbi(DS1302_PORT, DS1302_IO);
		}
		_delay_us(1);
		sbi(DS1302_PORT, DS1302_SCLK);
		_delay_us(2);
		cbi(DS1302_PORT, DS1302_SCLK);
	}
}

uint8_t ds1302GetByte(void) {
	uint8_t rxbyte = 0;
	uint8_t rxbit;
	int b;
	for (b = 0; b < 8; b++) {
		sbi(DS1302_PORT, DS1302_SCLK);			// Set clock to high
		_delay_us(1);
		cbi(DS1302_PORT, DS1302_SCLK);			// Set clock to low (transfer)
		_delay_us(1);
		rxbit = ((DS1302_PIN >> DS1302_IO) & 0x01);	// Read bit
		rxbyte |= (rxbit << b);					// Append bit to rxbyte
	}
	return(rxbyte);
}

// put this to one function that handles reading/writing and ram/rtc burst/non
void ds1302WriteReg(uint8_t reg, uint8_t data) {
	ds1302SetIOOutput();
	sbi(DS1302_PORT, DS1302_RST);
	_delay_us(3);
	ds1302SendByte(reg);
	ds1302SendByte(data);
	_delay_us(1);
	cbi(DS1302_PORT, DS1302_RST);
	_delay_us(4);
}

uint8_t ds1302ReadReg(uint8_t reg) {
	uint8_t rxbyte = 0;
	ds1302SetIOOutput();
	sbi(DS1302_PORT, DS1302_RST);
	_delay_us(4);
	reg |= DS1302_CMD_READ ;
	ds1302SendCommand(reg);
	ds1302SetIOInput();
	rxbyte = ds1302GetByte();
	cbi(DS1302_PORT, DS1302_RST);
	_delay_us(4);
	return(rxbyte);
}

/*************************************************************/
/********************* PUBLIC FUNCTIONS **********************/
/*************************************************************/

void ds1302Init(void) { // Change this function to take arguments for WP HF and TC
	ds1302InitHW(); // Initialize pins
	// Check if the write protect flag is on (and disable it)
	if ((ds1302ReadReg(DS1302_R_WP) & DS1302_WP_SET) == DS1302_WP_SET) {
		ds1302WriteReg(DS1302_R_WP, DS1302_WP_CLEAR);
	}
	uint8_t sec_r = ds1302ReadReg(DS1302_R_SECONDS);
	// Check if the Clock Halt flag is on (and disable it)
	// (Avoids overwriting other data on the seconds register)
	if ((sec_r & DS1302_HF_SET) == DS1302_HF_SET) {
		sec_r &= ~DS1302_HF_SET;
		ds1302WriteReg(DS1302_R_SECONDS, sec_r);
	}
	// Check if the Trickle Charger setting matches the configured one
	// (And change it so)
	if (ds1302ReadReg(DS1302_R_TC) != DS1302_TC_POWER_ON) {
		ds1302WriteReg(DS1302_R_TC, DS1302_TC_POWER_ON);
	}
}

// Sets the time from the rtctable to ds1302 registers
// using burst write mode
void ds1302SetTime(uint8_t* rtctable_ptr) {
	ds1302SetIOOutput();
	sbi(DS1302_PORT, DS1302_RST);
	_delay_us(3);
	ds1302SendByte(DS1302_R_CLKBRST);
	uint8_t value;
	int d;
	for (d = 0; d < 8; d++) {
		value = hex_to_bcd(rtctable_ptr[d]);
		ds1302SendByte(value);
		_delay_us(1);
	}
	cbi(DS1302_PORT, DS1302_RST);
	_delay_us(4);
}

// Gets the time from ds1302 registers to the rtctable
// using burst read mode
void ds1302GetTime(uint8_t* rtctable_ptr) {
	ds1302SetIOOutput();
	sbi(DS1302_PORT, DS1302_RST);
	_delay_us(4);
	uint8_t reg = DS1302_R_CLKBRST;
	reg |= DS1302_CMD_READ;
	ds1302SendCommand(reg);
	ds1302SetIOInput();
	uint8_t value;
	int d;
	for (d = 0; d < 8; d++) {
		value = ds1302GetByte();
		value &= DS1302_TC_BITMASKS[d];
		rtctable_ptr[d] = bcd_to_hex(value);
	}
	cbi(DS1302_PORT, DS1302_RST);
	_delay_us(4);
}

// Utility functions for hex -> bcd & bcd -> hex conversion
unsigned char hex_to_bcd(unsigned char hex) 
{ 
   unsigned char MSD = 0; 

   while (hex >= 10) 
   { 
      hex -= 10;   // hex becomes 1s (LSD) 
      MSD += 0x10;   // add 1 to 10s (MSD) 
   } 
   return MSD + hex;   // pack BCD into char 
} 

unsigned char bcd_to_hex(unsigned char bcd) 
{ 
   unsigned char hex = 10;   // set up to be multiplied by MSD 

   hex *= (bcd & 0xF0) >> 4;   // set 10s 
   hex += (bcd & 0x0F);      // add 1s 
   return hex; 
} 
